from wormbane.Feature import Doorway
from wormbane.Item import Corpse
from wormbane.language import DefiniteWrapper as the
from wormbane.move import move_item, move_monster
import random


def _passable(tile, thing, diagonal):
    return ((thing.env is tile
             or (not tile.monster
                 and (not tile.top_item or tile.top_item.passable)))
            and (not tile.feature
                 or (tile.feature.diagonally_passable if diagonal
                     else tile.feature.passable)))


def do_close_action(monster, door, game):
    if door.closed:
        raise ActionError()
    if door.tile.monster or door.tile.top_item:
        raise ActionError()
    game.write(the(monster), 'closes', the(door))
    door.locked = False
    door.closed = True
    monster.time += 1.0 / monster.speed


def do_hit_action(attacker, defender, game):
    game.write(the(attacker), 'hits', the(defender))
    vitality_loss = attacker.damage / defender.protection
    vitality_loss = random.uniform(vitality_loss / 2.0, vitality_loss)
    defender.vitality -= vitality_loss
    attacker.time += 1.0 / attacker.speed
    if defender.vitality <= 0.0:
        game.write(the(defender), 'dies')
        defender.alive = False
        env = defender.env
        move_monster(game, defender, None)
        game.current_level.remove_monster(defender)
        corpse = Corpse()
        corpse.color = defender.color
        move_item(game, corpse, env)


def do_kick_action(monster, door, game):
    game.write(the(monster), 'kicks', the(door))
    if random.randrange(2):
        game.write(the(door), 'splinters')
        door.env.feature = Doorway(dir = door.dir)
    monster.time += 1.0 / monster.speed


def do_move_action(monster, tile, game):
    offset = tile.pos - monster.pos
    diagonal = offset.x and offset.y
    if (_passable(monster.env, monster, diagonal)
        and _passable(tile, monster, diagonal)):
        move_monster(game, monster, tile)
        monster.time += abs(offset) / monster.speed
    else:
        raise ActionError()


def do_open_action(monster, door, game):
    if not door.closed or door.locked:
        raise ActionError()
    game.write(the(monster), 'opens', the(door))
    door.locked = False
    door.closed = False
    monster.time += 1.0 / monster.speed


def do_push_action(monster, item, game):
    offset = item.pos - monster.pos
    diagonal = offset.x and offset.y
    dest_pos = item.pos + offset
    if dest_pos not in game.current_level.bounds:
        raise ActionError()
    dest = game.current_level.tiles[dest_pos.x][dest_pos.y]
    if (not _passable(dest, item, diagonal)
        or not _passable(item.env, item, diagonal)
        or not _passable(monster.env, monster, diagonal)):
        raise ActionError()
    game.write(the(monster), 'pushes', the(item))
    monster_dest = item.env
    move_item(game, item, dest)
    if _passable(monster_dest, monster, diagonal):
        move_monster(game, monster, monster_dest)
    monster.time += abs(offset) / monster.speed


def do_rest_action(monster, game):
    game.write(the(monster), 'rests')
    monster.vitality = min(monster.vitality + 0.01, 1.0)
    monster.time += 1.0 / monster.speed


def do_search_action(monster, game):
    game.write(the(monster), 'searches')
    tiles = list(monster.tile.neighbors)
    random.shuffle(tiles)
    for tile in tiles:
        if random.randrange(2):
            finding = tile.search()
            if finding is not None:
                game.write(the(monster), 'finds', finding)
                break


class ActionError(Exception):
    pass
